package biolabs.mcl

/** Represents a reference to a model type from within that model's
  * definition. From within a model definition, one can reference
  * the exact type being defined, or, if the model has type parameters,
  * the model with actual type arguments substituted for type parameters
  * can be referenced.
  */
abstract class SelfRef(val declared: ModelDefinition) extends SingleType {
	val name = declared.name
	val typeArguments: List[MultiType]
	lazy val typeArgumentMap = declared.typeParameters map { _.name } zip typeArguments toMap

	// it is critical for this field to be lazy, since the SelfTypeDecl
	// object might not yet be fully constructed (i.e. we are called
	// from the SelfTypeDecl's constructor)
	lazy val actualSupertypes =
		declared.superTypes map { _.superType.substituteTypeVariables(typeArgumentMap) }

	override def getDelegateCandidates(interaction: ActualType): List[DelegateMethod] =
		throw new AbstractMethodError("SelfType should never be asked for delegate candidates")

	override def isAssignableFrom(sub: SingleType) =
		this == sub

	override def isDescendantOf(ancestor: Class[_]): Boolean = {
		declared.superTypes exists { _.superType.isDescendantOf(ancestor) }
	}

	override def getTypeArgumentsOf(ancestor: Class[_]): Seq[MultiType] = {
		if(!isDescendantOf(ancestor))
			throw new IllegalArgumentException(ancestor + " is not an ancestor of " + declared.name)
		for(supertype <- actualSupertypes) {
			if(supertype.isDescendantOf(ancestor))
				return supertype.getTypeArgumentsOf(ancestor)
		}
		throw new AssertionError("unreachable code");
	}

	def substituteTypeVariables(substitutions: Map[String, MultiType]): SelfRef = {
		val newArgs = typeArguments map { _.substituteTypeVariables(substitutions) }
		new ParameterizedSelfRef(declared, newArgs)
	}

	override def hashCode(): Int =
		declared.hashCode() * 31 + typeArguments.hashCode()

	override def equals(other: Any): Boolean = other match {
		case that: SelfRef => isEqualTo(that, Nil)
		case _ => false
	}

	override def isEqualTo(other: SingleType, comparing: List[(TypeVar, TypeVar)]) = other match {
		case that: SelfRef => isEqualTo(that, comparing)
		case _ => false
	}

	private def isEqualTo(that: SelfRef, comparing: List[(TypeVar, TypeVar)]) = {
		declared == that.declared && typeArguments.corresponds(that.typeArguments)((a, b) => a.isEqualTo(b, comparing))
	}
}

class ExactSelfRef(override val declared: ModelDefinition) extends SelfRef(declared) {
	lazy val typeArguments = declared.typeParameters map { MultiType(_) }
}

class ParameterizedSelfRef(override val declared: ModelDefinition, val typeArguments: List[MultiType]) extends SelfRef(declared) {
	assert(declared.ast.typeParams.length == typeArguments.length)
}